using System;
using System.Collections.Generic;
using GameFramework.Core;
using UnityEngine;
using Object = UnityEngine.Object;

namespace GameFramework.Update
{
    /// <summary>
    /// 帧更新服务实现
    /// </summary>
    internal sealed partial class UpdateServiceImpl : IInitialize, IDisposable, IUpdateService
    {
        private Driver m_Driver;

        private readonly List<IUpdate> m_UpdateList = new();
        private readonly List<ILateUpdate> m_LateUpdateList = new();
        private readonly List<IFixedUpdate> m_FixedUpdateList = new();
        private readonly List<Action> m_DelayCallList = new();

        private int m_UpdateIndex;
        private int m_LateUpdateIndex;
        private int m_FixedUpdateIndex;
        private int m_DelayCallIndex;
        
        public void Initialize()
        {
            m_Driver = new GameObject("Update Driver").AddComponent<Driver>();
            m_Driver.OnUpdated += OnUpdated;
            m_Driver.OnLateUpdated += OnLateUpdated;
            m_Driver.OnFixedUpdated += OnFixedUpdated;
            m_Driver.OnUpdated += OnExecuteDelayCallActions;
        }

        public void Dispose()
        {
            if (m_Driver == null)
                return;

            m_Driver.OnUpdated -= OnUpdated;
            m_Driver.OnLateUpdated -= OnLateUpdated;
            m_Driver.OnFixedUpdated -= OnFixedUpdated;
            m_Driver.OnUpdated -= OnExecuteDelayCallActions;
            Object.Destroy(m_Driver.gameObject);
        }

        public void RegisterUpdate(IUpdate update)
        {
            if (m_UpdateList.Contains(update))
                return;

            m_UpdateList.Add(update);
        }

        public void UnRegisterUpdate(IUpdate update)
        {
            var index = m_UpdateList.IndexOf(update);
            if (index < 0)
                return;

            m_UpdateList.RemoveAt(index);

            // 若移除的index在当前遍历序号之前 则需要令遍历序号-1
            if (index < m_UpdateIndex)
                --m_UpdateIndex;
        }

        public void RegisterLateUpdate(ILateUpdate update)
        {
            if (m_LateUpdateList.Contains(update))
                return;

            m_LateUpdateList.Add(update);
        }

        public void UnRegisterLateUpdate(ILateUpdate update)
        {
            var index = m_LateUpdateList.IndexOf(update);
            if (index < 0)
                return;

            m_LateUpdateList.RemoveAt(index);

            // 若移除的index在当前遍历序号之前 则需要令遍历序号-1
            if (index < m_LateUpdateIndex)
                --m_LateUpdateIndex;
        }

        public void RegisterFixedUpdate(IFixedUpdate update)
        {
            if (m_FixedUpdateList.Contains(update))
                return;

            m_FixedUpdateList.Add(update);
        }

        public void UnRegisterFixedUpdate(IFixedUpdate update)
        {
            var index = m_FixedUpdateList.IndexOf(update);
            if (index < 0)
                return;

            m_FixedUpdateList.RemoveAt(index);

            // 若移除的index在当前遍历序号之前 则需要令遍历序号-1
            if (index < m_FixedUpdateIndex)
                --m_FixedUpdateIndex;
        }

        public void RegisterDelayCall(Action action)
        {
            if (m_DelayCallList.Contains(action))
                return;

            m_DelayCallList.Add(action);
        }

        public void UnRegisterDelayCall(Action action)
        {
            var index = m_DelayCallList.IndexOf(action);
            if (index < 0)
                return;

            m_DelayCallList.RemoveAt(index);

            // 若移除的index在当前遍历序号之前 则需要令遍历序号-1
            if (index < m_DelayCallIndex)
                --m_DelayCallIndex;
        }

        private void OnUpdated(float elapseSeconds, float realElapseSeconds)
        {
            if (m_UpdateList.Count == 0)
                return;

            m_UpdateIndex = 0;

            while (m_UpdateIndex < m_UpdateList.Count)
            {
                try
                {
                    m_UpdateList[m_UpdateIndex].OnUpdate(elapseSeconds, realElapseSeconds);
                }
                catch (Exception e)
                {
                    Log.Exception(e);
                }

                ++m_UpdateIndex;
            }

            m_UpdateIndex = -1;
        }

        private void OnLateUpdated(float elapseSeconds, float realElapseSeconds)
        {
            if (m_LateUpdateList.Count == 0)
                return;

            m_LateUpdateIndex = 0;

            while (m_LateUpdateIndex < m_LateUpdateList.Count)
            {
                try
                {
                    m_LateUpdateList[m_LateUpdateIndex].OnLateUpdate(elapseSeconds, realElapseSeconds);
                }
                catch (Exception e)
                {
                    Log.Exception(e);
                }

                ++m_LateUpdateIndex;
            }

            m_LateUpdateIndex = -1;
        }

        private void OnFixedUpdated(float elapseSeconds, float realElapseSeconds)
        {
            if (m_FixedUpdateList.Count == 0)
                return;

            m_FixedUpdateIndex = 0;

            while (m_FixedUpdateIndex < m_FixedUpdateList.Count)
            {
                try
                {
                    m_FixedUpdateList[m_FixedUpdateIndex].OnFixedUpdate(elapseSeconds, realElapseSeconds);
                }
                catch (Exception e)
                {
                    Log.Exception(e);
                }

                ++m_FixedUpdateIndex;
            }

            m_FixedUpdateIndex = -1;
        }

        private void OnExecuteDelayCallActions(float _, float __)
        {
            if (m_DelayCallList.Count == 0)
                return;

            m_DelayCallIndex = 0;

            while (m_DelayCallIndex < m_DelayCallList.Count)
            {
                try
                {
                    m_DelayCallList[m_DelayCallIndex].Invoke();
                }
                catch (Exception e)
                {
                    Log.Exception(e);
                }

                ++m_DelayCallIndex;
            }

            m_DelayCallIndex = -1;
        }
    }
}